# -*- coding: utf-8 -*-

# This code is licensed under GNU GPL v3: http://www.gnu.org/copyleft/gpl.html

# Some parts of code have been borrowed from yaro: http://lukearno.com/projects/yaro/

class storage(dict):
    """A generic storage with both dict- and attr-style access."""
    def __getattr__(self, k):
        return self[k] if k in self.keys() else None

    def __setattr(self, k, v):
        self[k] = v

    def __delattr__(self, k):
        del self[k]

class headers(storage):
    """A class for retrieving and storing the HTTP headers."""
    def __init__(self, env):
        for k, v in env:
            if k.startswith('HTTP_'):
                self[k[5:]] = v

    __getattr__ = lambda self, k: super(headers, self).__getattr__(k.lower())
    __setattr__ = lambda self, k, v: super(headrers, self).__setattr__(k.lower(), v.lower())
    __delattr__ = lambda self, k: super(headers, self).__delattr__(k.lower())
    has_key = lambda self, k: super(headers, self).has_key(k.lower())

class out_headers(headers):
    """A class for storing response HTTP headers."""
    def __init__(self, env):
        pass

def uri(env):
    """A function getting all the URI data out of env."""
    this = Storage()
    this.scheme = env['wsgi.url_scheme']
    if environ.get('HTTP_HOST'):
        this.host = environ['HTTP_HOST']
        if ':' in this.host:
            this.host, this.port = this.host.split(':')
        else:
            if this.scheme == 'http':
                this.port = '80'
            else:
                this.port = '443'
    else:
        this.host += environ['SERVER_NAME']
        this.port += environ['SERVER_PORT']
    this.script = environ.get('SCRIPT_NAME', '')
    this.path = environ.get('PATH_INFO', '')
    this.query = environ.get('QUERY_STRING', '')

    this.query = unquote()

    return this

class request(storage):
    """A request object class."""
    __getters = [
        ('env', lambda env: env),
        ('method', 'REQUEST_METHOD'),
        ('mimetype', 'CONTENT_TYPE'),
        ('length', 'CONTENT_LENGTH'),
        ('uri', uri),
        ('body', 'wsgi.input'),
        ('headers', headers),
        ('out_headers', out_headers)
    ]

    def __init__(self, env, getters=[]):

        self.__getters.extend(getters)

        for getter in self.__getters:

            if len(getter) == 2 and type(getter[1]) in (str, unicode):
                self[getter[0]] = env.get(getter[1], '')

            elif len(getter) == 2 and callable(getter[1]):
                self[getter[0]] = getter[1](env)

            elif len(getter) == 3 and all([True if type(g) in (str, unicode) else False for g in getter]):
                self[getter[0]] = env.get(getter[1], getter[2])

http_codes = {
    '100': '100 Continue',
    '101': '101 Switching Protocols',
    '200': '200 OK',
    '201': '201 Created',
    '202': '202 Accepted',
    '203': '203 Non-Authoritative Information',
    '204': '204 No Content',
    '205': '205 Reset Content',
    '206': '206 Partial Content',
    '300': '300 Multiple Choices',
    '301': '301 Moved Permanently',
    '302': '302 Found',
    '303': '303 See Other',
    '304': '304 Not Modified',
    '305': '305 Use Proxy',
    # 306 is unused but reserved for future use. (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
    '307': '307 Temporary Redirect',
    '400': '400 Bad Request',
    '401': '401 Unauthorized',
    '402': '402 Payment Required',
    '403': '403 Forbidden',
    '404': '404 Not Found',
    '405': '405 Method Not Allowed',
    '406': '406 Not Acceptable',
    '407': '407 Proxy Authentication Required',
    '408': '408 Request Timeout',
    '409': '409 Conflict',
    '410': '410 Gone',
    '411': '411 Length Required',
    '412': '412 Precondition Failed',
    '413': '413 Request Entity Too Large',
    '414': '414 Request-URI Too Long',
    '415': '415 Unsupported Media Type',
    '416': '416 Request Range Not Satisfiable',
    '417': '417 Expectation Failed',
    '500': '500 Internal Server Error',
    '501': '501 Not Implemented',
    '502': '502 Bad Gateway',
    '503': '503 Service Unavailable',
    '504': '504 Gateway Timeout',
    '505': '505 HTTP Version Not Supported'}

class http(Exception):
    def __init__(self, code, *args):
        super(http, self).__init__(self, str(code), *args)


class handler(object):
    """Handler base class.

    handler.deco can be used as a decorator on function handlers."""
    magic = []

    @classmethod
    def wrapper(clas, app, env, start_response, magic=[]):

        req = request(env, cls.magic)

        try:
            out = app(req)

        except http as err:
            start_response(codes[err.message], [])
            return ()

        else:
            heads = req.out_headers

            if out is None: # In case of no output.
                heads['Content-Length'] = '0'
                out = ''

            if isinstance(out, unicode):
                out = out.encode('utf-8')

            heads['Content-Length'] = len(out)

            start_response(codes['200'], dict(heads))
            return (out)

    @classmethod
    def deco(cls, arg):

        if callable(arg):
            return lambda env, start_response: cls.wrapper(arg, env, start_response)

        elif type(arg) in (list, tuple):
            return lambda app: (lambda env, start_response: cls.wrapper(app, env, start_reps, arg))

        else:
            raise ValueError('Improper argument type!')

    @classmethod
    def __call__(cls, env, start_response):
            return cls.wrapper(cls.__dict__[env['REQUEST_METHOD']], env, start_response)

class routes(object):
    """A WSGI router."""
    def __init__(self, mapping, mount=''):
        self.map = []
        self.nameds = {}
        self.mount = mount

        for no, route in enumerate(mapping):
            if len(route) >= 2:
                self.map.append((route[0].strip().strip('/').split('/'), route[1]))
            if len(route) == 3:
                self.nameds[route[2]] = len(self.map) - 1

    def url_for(self, name, k={}):
        route, handler = self.map[self.nameds[name]]

        return '/'.join([sect if not sect.startswith(':') else k[sect[1:]] for sect in route])

    def check_route(self, route, path):
        path = path.strip().strip('/')
        ok, args = True, {}

        if len(route) != len(path):

            for r, p in zip(route, path):
                if r.startswith(':'):
                    args[r[1:]] = p

                elif r == p:
                    pass

                else:
                    ok = False

        return (ok, args)


    def __call__(self, env, start_response):
        path, ok, route_args = env.get('PATH_INFO', '').split('/'), True, {}

        for route, handler in self.map:

            test = self.check_route(route, path)

            if test[0]:
                return handler(env.update({'routes': test[0]}), start_response)

        start_response(http_codes['404'], [])
        return []

stack = lambda layers: reduce(lambda middle, app: middle(app), layers)
stack.__doc__ = """A function that stacks WSGI middleware and the final app."""